package com.starhub.sample.dao.impl;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hibernate.SessionFactory;
import org.hibernate.classic.Session;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.orm.ObjectRetrievalFailureException;

import com.google.inject.internal.Preconditions;
import com.starhub.sample.dao.GenericDAO;

/**
 * @param <T>
 *            a type variable
 * @param <PK>
 *            the primary key for that type
 */
public class GenericDAOImpl<T, PK extends Serializable> implements GenericDAO<T, PK> {

    private static final String UNCHECKED = "unchecked";

    /**
     * Log variable for all child classes. Uses LogFactory.getLog(getClass())
     * from Commons Logging
     */
    private final Log log = LogFactory.getLog(getClass());
    private Class<T> persistentClass;

    private SessionFactory sessionFactory;

    @Autowired
    @Required
    public void setSessionFactory(SessionFactory sessionFactory) {

	this.sessionFactory = sessionFactory;
    }

    public SessionFactory getSessionFactory() {

	return this.sessionFactory;
    }

    protected Session getCurrentSession() {

	return sessionFactory.getCurrentSession();
    }

    /**
     * Constructor that takes in a class to see which type of entity to persist.
     * Use this constructor when subclassing.
     * 
     * @param persistentClass
     *            the class type you'd like to persist
     */
    public GenericDAOImpl(final Class<T> persistentClass) {

	this.persistentClass = persistentClass;
    }

    /**
     * Constructor that takes in a class and sessionFactory for easy creation of
     * DAO.
     * 
     * @param persistentClass
     *            the class type you'd like to persist
     * @param sessionFactory
     *            the pre-configured Hibernate SessionFactory
     */
    public GenericDAOImpl(final Class<T> persistentClass, SessionFactory sessionFactory) {

	this.persistentClass = persistentClass;
	this.sessionFactory = sessionFactory;
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings(UNCHECKED)
    public List<T> getAll() {

	if (log.isDebugEnabled()) {
	    log.debug("::getAll()");
	}
	return getCurrentSession().createQuery("from " + this.persistentClass.getName()).list();
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public List<T> getAllDistinct() {

	Collection result = new LinkedHashSet(getAll());
	return new ArrayList(result);
    }

    /**
     * {@inheritDoc}
     */
    @SuppressWarnings(UNCHECKED)
    public T get(final PK id) {

	Preconditions.checkArgument(id != null);
	T entity = (T) getCurrentSession().get(this.persistentClass, id);

	if (entity == null) {
	    log.warn(this.persistentClass + "' object with id '" + id + "' not found...");
	    throw new ObjectRetrievalFailureException(this.persistentClass, id);
	}

	return entity;
    }

    /**
     * {@inheritDoc}
     */
    public T save(final T entity) {

	Preconditions.checkNotNull(entity);
	this.getCurrentSession().persist(entity);
	return entity;
    }

    public void update(T entity) {

	Preconditions.checkNotNull(entity);
	this.getCurrentSession().merge(entity);
    }

    public T saveOrUpdate(T entity) {
	Preconditions.checkNotNull(entity);
	this.getCurrentSession().saveOrUpdate(entity);
	return entity;
    }

    /**
     * {@inheritDoc}
     */
    public void delete(final T entity) {

	Preconditions.checkNotNull(entity);
	this.getCurrentSession().delete(entity);
    }

    public void deleteById(final PK id) {

	final T entity = this.get(id);
	Preconditions.checkState(entity != null);
	this.delete(entity);
    }

}
